
Imports std

Public MustInherit Class procIOHandleBase
    Public Event outputReceive(ByVal s As String, ByVal exited As Boolean)
    Public Event errorReceive(ByVal s As String, ByVal exited As Boolean)
    Public Const maxBuffLen As Int32 = 16384

    Protected Friend _bufflen As Int32
    Protected Friend _errorThread As Threading.Thread
    Protected Friend _outputThread As Threading.Thread
    Protected Friend _errorInThread As Threading.Thread
    Protected Friend _outputInThread As Threading.Thread
    Protected Friend _outstream As circle(Of Char)
    Protected Friend _errstream As circle(Of Char)
    'Protected Friend _stopsignal As Boolean
    Protected Friend WithEvents _proc As Process
    Public waittime As Int64 = 100

    Protected MustOverride Function readOutputChar(ByRef c As Char) As Boolean
    Protected MustOverride Function readErrorChar(ByRef c As Char) As Boolean
    Protected MustOverride Function readOutputToEnd(ByRef s As String) As Boolean
    Protected MustOverride Function readErrorToEnd(ByRef s As String) As Boolean
    Protected MustOverride Sub writeInput(ByRef s As String)
    Protected MustOverride Sub closeOutput()
    Protected MustOverride Sub closeError()

    Public Overridable Property process() As Process
        Get
            Return _proc
        End Get
        Set(ByVal value As Process)
            _proc = value
        End Set
    End Property

    Public Property bufflen() As Int32
        Get
            Return _bufflen
        End Get
        Set(ByVal value As Int32)
            If value < 0 Or value >= maxBuffLen Then
                _bufflen = maxBuffLen
            Else
                _bufflen = value
            End If
        End Set
    End Property

    Protected Friend Sub errorHandle()
        'On Error GoTo ext

        Dim buff() As Char = Nothing
        While Not _proc Is Nothing AndAlso Not _proc.HasExited()
            If Not _errstream.empty() Then
                assert(_errstream.pop_front(buff), "_errstream.pop_front() returns false.")
                RaiseEvent errorReceive(buff, False)
            Else
                sleep(waittime)
                'sleep only when empty()
            End If
        End While

ext:
    End Sub

    Protected Friend Sub outputHandle()
        'On Error GoTo ext

        Dim buff() As Char = Nothing
        While Not _proc Is Nothing AndAlso Not _proc.HasExited()
            If Not _outstream.empty() Then
                assert(_outstream.pop_front(buff), "_outstream.pop_front() returns false.")
                RaiseEvent outputReceive(buff, False)
            Else
                sleep(waittime)
                'sleep only when empty()
            End If
        End While

ext:
    End Sub

    Protected Friend Sub errorInHandle()
        Dim c As Char
        While Not _proc Is Nothing AndAlso Not _proc.HasExited()
            If readErrorChar(c) Then
                While Not _errstream.push_back(c)
                    sleep(waittime)
                End While
            Else
                sleep(waittime)
                'assume when no output now, will have no output during the next 100ms
            End If
        End While

ext:
    End Sub

    Protected Friend Sub outputInHandle()
        'On Error GoTo ext

        Dim c As Char
        While Not _proc Is Nothing AndAlso Not _proc.HasExited()
            If readOutputChar(c) Then
                While Not _outstream.push_back(c)
                    sleep(waittime)
                End While
            Else
                sleep(waittime)
                'assume when no output now, will have no output during the next 100ms
            End If
        End While

ext:
    End Sub

    Protected Friend Sub initial()
        _proc = Nothing
        _bufflen = maxBuffLen
        _outstream = New circle(Of Char)
        _errstream = New circle(Of Char)
    End Sub

    Public Sub input(ByRef s As String)
        assert(Not _proc Is Nothing, "_proc is nothing, cannot input.")
        writeInput(s)
    End Sub

    Public Sub start()
        If _proc Is Nothing Then
            _errorHandle.raiseError("_proc is nothing, cannot start. [exited before start?]" _
                                        , Global.errorHandle.errorType.system)
        End If
        _proc.EnableRaisingEvents = True
        _outstream.clear()
        _errstream.clear()
        _errorThread = New Threading.Thread(AddressOf errorHandle)
        _outputThread = New Threading.Thread(AddressOf outputHandle)
        _errorThread.Start()
        _outputThread.Start()
        _errorInThread = New Threading.Thread(AddressOf errorInHandle)
        _outputInThread = New Threading.Thread(AddressOf outputInHandle)
        _errorInThread.Start()
        _outputInThread.Start()
    End Sub

    Public Sub [stop]()
        _proc.WaitForExit()
    End Sub

    Private Sub process_Exited(ByVal sender As Object, ByVal e As System.EventArgs) Handles _proc.Exited
        On Error Resume Next

        Dim errout As String = emptyString
        readErrorToEnd(errout)
        Dim outout As String = emptyString
        readOutputToEnd(outout)
        closeError()
        closeOutput()
        _proc = Nothing

        assert(_errstream.push_back(errout.ToCharArray(), 0, -1, True), "_errstream.push_back() returns false.")
        assert(_outstream.push_back(outout.ToCharArray(), 0, -1, True), "_outstream.push_back() returns false.")

        Dim buff() As Char = Nothing
        assert(_errstream.pop_front(buff), "_errstream.pop_front() returns false.")
        RaiseEvent errorReceive(buff, True)
        assert(_outstream.pop_front(buff), "_outstream.pop_front() returns false.")
        RaiseEvent outputReceive(buff, True)
    End Sub

    Public Sub New()
        initial()
    End Sub
End Class

#If Not (PocketPC OrElse Smartphone) Then

Public Class procIOHandle
    Inherits procIOHandleBase

    Public Overrides Property process() As System.Diagnostics.Process
        Get
            Return MyBase.process
        End Get
        Set(ByVal value As System.Diagnostics.Process)
            MyBase.process = value
            _proc.StartInfo.UseShellExecute = False
            _proc.StartInfo.RedirectStandardInput = True
            _proc.StartInfo.RedirectStandardOutput = True
            _proc.StartInfo.RedirectStandardError = True
        End Set
    End Property

    Protected Overrides Sub closeError()
        _proc.StandardError.Close()
    End Sub

    Protected Overrides Sub closeOutput()
        _proc.StandardOutput.Close()
    End Sub

    Protected Overrides Function readErrorChar(ByRef c As Char) As Boolean
        Try
            c = Convert.ToChar(_proc.StandardError.Read())
            Return True
        Catch
            Return False
        End Try
    End Function

    Protected Overrides Function readOutputChar(ByRef c As Char) As Boolean
        Try
            c = Convert.ToChar(_proc.StandardOutput.Read())
            Return True
        Catch
            Return False
        End Try
    End Function

    Protected Overrides Sub writeInput(ByRef s As String)
        _proc.StandardInput.Write(s)
    End Sub

    Protected Overrides Function readErrorToEnd(ByRef s As String) As Boolean
        Try
            s = _proc.StandardError.ReadToEnd()
            Return True
        Catch
            Return False
        End Try
    End Function

    Protected Overrides Function readOutputToEnd(ByRef s As String) As Boolean
        Try
            s = _proc.StandardOutput.ReadToEnd()
            Return True
        Catch
            Return False
        End Try
    End Function
End Class

#End If
